const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect } = require("chai");
const { PoolStatus } = require("./utils");

describe("Pool", function () {
  async function deployFixture() {
    const [deployer, user, trader] = await ethers.getSigners();

    const ACLManagerFactory = await ethers.getContractFactory("ACLManager");
    const ACLManager = await ACLManagerFactory.deploy(deployer);
    await ACLManager.deploymentTransaction().wait();

    const registerTx = await ACLManager.connect(deployer).registerTraders([trader]);
    await registerTx.wait();

    const mintAmount = ethers.parseEther("100");
    const TokenFactory = await ethers.getContractFactory("Token");
    const tokenA = await TokenFactory.deploy("TestTokenA", "TTA", mintAmount);
    await tokenA.deploymentTransaction().wait();

    const DeltaNeutralPortfolioFactory = await ethers.getContractFactory("DeltaNeutralPortfolio");
    const DNP = await DeltaNeutralPortfolioFactory.deploy(ACLManager, [tokenA], [], []);
    await DNP.deploymentTransaction().wait();

    const PoolFactory = await ethers.getContractFactory("Pool");
    const pool = await PoolFactory.deploy(ACLManager, tokenA, DNP);
    await pool.deploymentTransaction().wait();

    const registerPoolTx = await DNP.connect(deployer).registerPool(pool);
    await registerPoolTx.wait();

    return { ACLManager, tokenA, DNP, pool, deployer, user, trader };
  }

  describe("Deployment", function () {
    it("should deploy", async function () {
      const { ACLManager, tokenA, DNP, pool } = await loadFixture(deployFixture);

      expect(await pool.ACLManager()).to.equal(ACLManager);
      expect(await pool.baseToken()).to.equal(tokenA);
      expect(await pool.investmentManager()).to.equal(DNP);
      expect(await pool.currentStatus()).to.equal(PoolStatus.FUNDRAISING);
    });
  });

  describe("Deposits", function () {
    it("User deposit funds", async function () {
      const { tokenA, pool, deployer, user } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);
    });

    it("Revert deposit with insufficient allowance", async function () {
      const { tokenA, pool, deployer, user } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool without approval
      await expect(pool.connect(user).deposit(amount)).to.be.revertedWith("ERC20: insufficient allowance");
    });

    it("Revert if amount is 0", async function () {
      const { pool, user } = await loadFixture(deployFixture);

      // deposit with 0 amount
      await expect(pool.connect(user).deposit(0)).to.be.revertedWith("Pool: deposit: amount is zero");
    });

    it("Revert if status is not Status.FUNDRAISING", async function () {
      const { pool, deployer, user } = await loadFixture(deployFixture);

      const startTx = await pool.connect(deployer).launchPool();
      await startTx.wait();

      // deposit to the LAUNCHED pool
      await expect(pool.connect(user).deposit(0)).to.be.revertedWith("Pool: deposit: current status is not FUNDRAISING");
    });
  });

  describe("launchPool", function () {
    it("launchPool by admin", async function () {
      const { tokenA, pool, DNP, deployer, user } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(deployer).launchPool();
      await launchTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);
    });

    it("launchPool by trader", async function () {
      const { tokenA, pool, DNP, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(trader).launchPool();
      await launchTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);
    });

    it("Reverted if caller is not protocol admin or trader", async function () {
      const { tokenA, pool, DNP, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      await expect(pool.connect(user).launchPool()).to.be.revertedWith("Pool: caller is not protocol trader");
    });

    it("Reverted if current status is not FUNDRAISING", async function () {
      const { tokenA, pool, DNP, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(trader).launchPool();
      await launchTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);

      await expect(pool.connect(trader).launchPool()).to.be.revertedWith("Pool: launchPool: current status is not FUNDRAISING");
    });
  });

  describe("finishPool", function () {
    it("finishPool by admin", async function () {
      const { tokenA, pool, DNP, deployer, user } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(deployer).launchPool();
      await launchTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);

      // finish pool
      const finishTx = await pool.connect(deployer).finishPool();
      await finishTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.FINISHED);
    });

    it("launchPool by trader", async function () {
      const { tokenA, pool, DNP, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(trader).launchPool();
      await launchTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);

      // finish pool
      const finishTx = await pool.connect(trader).finishPool();
      await finishTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.FINISHED);
    });

    it("Reverted if caller is not protocol admin or trader", async function () {
      const { tokenA, pool, DNP, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(trader).launchPool();
      await launchTx.wait();

      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);

      // finish pool
      await expect(pool.connect(user).finishPool()).to.be.revertedWith("Pool: caller is not protocol trader");
    });

    it("Reverted if current status is not LAUNCHED", async function () {
      const { tokenA, pool, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      await expect(pool.connect(trader).finishPool()).to.be.revertedWith("Pool: finishPool: current status is not LAUNCHED");
    });
  });

  describe("Withdraw", function () {
    it("User withdraw funds", async function () {
      const { tokenA, pool, deployer, user, trader } = await loadFixture(deployFixture);

      // transfer funds to user
      const amount = ethers.parseEther("10");
      const txTransfer = await tokenA.connect(deployer).transfer(user, amount);
      await txTransfer.wait();

      const userBalance = await tokenA.balanceOf(user);
      expect(userBalance).to.equal(amount);

      // deposit funds to the pool
      const txApprove = await tokenA.connect(user).approve(pool, amount);
      await txApprove.wait();
      const txDeposit = await pool.connect(user).deposit(amount);
      await txDeposit.wait();

      const deposit = await pool.deposits(user);
      expect(deposit).to.equal(amount);

      // launch pool
      const launchTx = await pool.connect(trader).launchPool();
      await launchTx.wait();
      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);

      // finish pool
      const finishTx = await pool.connect(trader).finishPool();
      await finishTx.wait();
      expect(await pool.currentStatus()).to.equal(PoolStatus.FINISHED);

      // withdraw funds
      const withdrawTx = await pool.connect(user).withdraw();
      await withdrawTx.wait();
      const userBalanceAfter = await tokenA.balanceOf(user);
      expect(userBalanceAfter).to.equal(amount);
    });

    it("Revert if user deposit is 0", async function () {
      const { pool, user, trader } = await loadFixture(deployFixture);

      // launch pool
      const launchTx = await pool.connect(trader).launchPool();
      await launchTx.wait();
      expect(await pool.currentStatus()).to.equal(PoolStatus.LAUNCHED);

      // finish pool
      const finishTx = await pool.connect(trader).finishPool();
      await finishTx.wait();
      expect(await pool.currentStatus()).to.equal(PoolStatus.FINISHED);

      // withdraw funds
      await expect(pool.connect(user).withdraw()).to.be.revertedWith("Pool: withdraw: user deposit is zero");
    });

    it("Revert if status is not Status.FINISHED", async function () {
      const { pool, deployer } = await loadFixture(deployFixture);

      // deposit to the LAUNCHED pool
      await expect(pool.connect(deployer).withdraw()).to.be.revertedWith("Pool: withdraw: current status is not FINISHED");
    });
  });
});
